<template>
  <div class="z-tree-transfer">
    <!-- 左边 -->
    <div class="left-content">
      <div class="list">
        <div class="left_lowline">
          <el-checkbox v-model="isCheckedAllLeft" :disabled="isLeftCheckAllBoxDisabled" label="" size="large"
                       @change="handleLeftAllCheck"/>
          <p class="left_title">{{ leftTitle }}</p>
        </div>
        <!-- 搜索 -->
        <div class="left_input">
          <el-input
              v-model="leftFilterText"
              class="w-50 m-2"
              placeholder="搜索"
              clearable
              :prefix-icon="Search"
          />
        </div>
        <div class="left-tree">
          <el-tree
              ref="leftTreeRef"
              :data="leftTreeData"
              show-checkbox
              :filter-node-method="filterLeftNode"
              :node-key="nodeKey"
              highlight-current
              :default-expand-all="defaultExpandAll"
              :expand-on-click-node="expandOnClickNode"
              :props="defaultProps"
              v-slot="{ node, data }"
              :check-on-click-node="checkOnClickNode"
              @check-change="handleLeftCheckChange"
          />
        </div>
      </div>
    </div>
    <!-- 中间按钮 -->
    <div class="btn-div">
      <div class="btn-item" @click="toRight()">
        <el-button :disabled="!currentLeftUseableNodes.length" :icon="ArrowRight" type="primary" size="large"/>
      </div>
      <div class="btn-item" @click="toLeft()">
        <el-button :disabled="isToLeftBtnDisabled" :icon="ArrowLeft" type="primary" size="large"/>
      </div>
    </div>
    <!-- 右边 -->
    <div class="righ-content">
      <div class="list">
        <div class="left_lowline">
          <el-checkbox v-model="isCheckedAllRight" :disabled="isRightCheckAllBoxDisabled" label="" size="large"
                       @change="handleRightAllCheck"/>
          <p class="left_title">{{ rightTitle }}</p>
        </div>
        <!-- 搜索 -->
        <div class="left_input">
          <el-input
              v-model="rightFilterText"
              class="w-50 m-2"
              placeholder="搜索"
              clearable
              :prefix-icon="Search"
          />
        </div>


        <!--    右侧数据展示格式为list时    -->
        <div v-if="isToList">
          <!--   根据[props.nodeKey]排序  ；  根据rightFilterText进行过滤显示    -->
          <div
              class="right_item"
              v-if="sortRightListByKey(rightData).filter(item => item[defaultProps.label].includes(rightFilterText)).length"
              v-for="(item, index) in sortRightListByKey(rightData).filter(item => item[defaultProps.label].includes(rightFilterText))"
              :key="index"
          >
            <!-- 检查是否有名为 "right-item" 的插槽内容 -->
            <slot name="right-item" :item="item" :index="index" v-if="$slots['right-item']"></slot>
            <!-- 如果没有，则显示默认内容 -->
            <div v-else>
              <el-checkbox
                  v-model="item.checked"
                  :value="item[nodeKey]"
                  :true-label="true"
                  :false-label="false"
              >{{ item[defaultProps.label] }}
              </el-checkbox>
            </div>
          </div>

          <div v-else style="padding: 10px">
            <el-text type="info">暂无数据</el-text>
          </div>
        </div>

        <!--    右侧数据展示格式为tree时    -->
        <div v-else class="right-tree">
          <el-tree
              ref="rightTreeRef"
              :data="rightTreeData"
              show-checkbox
              :filter-node-method="filterRightNode"
              :node-key="nodeKey"
              highlight-current
              :default-expand-all="defaultExpandAll"
              :expand-on-click-node="expandOnClickNode"
              :props="defaultProps"
              v-slot="{ node, data }"
              :check-on-click-node="checkOnClickNode"
              @check-change="handleRightCheckChange"
          />
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
//@ts-nocheck    解决ts类型报红
import {computed, nextTick, onMounted, ref, watch} from 'vue';
import {ArrowLeft, ArrowRight, Search} from '@element-plus/icons-vue';

import {
  checkedAllTrue,
  checkAllDisabled,
  setFieldValue,
  setTreeIsDisabled,
  getDisabledNodeIds,
  updateDisabledStatus,
  deepClone

} from '@/utils'

/* 定义props */
const props = defineProps({
  // 主键
  nodeKey: {
    type: String,
    default: 'id'
  },
  // 左侧标题
  leftTitle: {
    type: String,
    default: () => {
      return '全部列表'
    }
  },
  // 右侧标题
  rightTitle: {
    type: String,
    default: () => {
      return '已选列表'
    }
  },
  // tree绑定的props
  defaultProps: {
    type: Object,
    default: () => {
      return {
        label: 'label',
        children: 'children',
        disabled: 'disabled',
      }
    }
  },
  // 左侧树结构数据
  leftData: {
    type: Array,
    default: () => {
      return []
    }
  },
  // 默认选中的数据的ids，显示在右侧列表
  defaultSelectionKeys: {
    type: Array,
    default: () => {
      return []
    }
  },
  // 右侧数据是否按顺序排序 仅在平铺展开是有效  只支持按住键正序排序
  isSort: {
    type: Boolean,
  },
  defaultExpandAll: {
    type: Boolean,
    default: false
  },
  // 左侧选择数据移动到右边的是叶子结点还是全部节点
  // 默认选择并返回叶子结点
  // isSelectAllNodes: {
  //   type: Boolean,
  //   default: false
  // },
  // 是否在点击节点的时候选中节点，默认值为 false，即只有在点击复选框时才会选中节点。
  checkOnClickNode: {
    type: Boolean,
    default: false
  },
  // 是否在点击节点的时候展开或者收缩节点， 默认值为 true，如果为 false，则只有点箭头图标的时候才会展开或者收缩节点。
  expandOnClickNode: {
    type: Boolean,
    default: true
  },
  // 选择右侧所选数据的展示类型，默认是tree，true时为list
  isToList: {
    type: Boolean,
    default: false
  },

});

const isCheckedAllLeft = ref(false) // 左侧全选框是否选中
const isCheckedAllRight = ref(false)  // 右侧全选框是否选中

const isLeftCheckAllBoxDisabled = ref(false) // 左侧全选框是否禁用
const isRightCheckAllBoxDisabled = ref(false) // 右侧全选框是否禁用

const leftTreeRef = ref(); // 左侧树ref
const rightTreeRef = ref(); // 右侧树ref


const leftFilterText = ref(''); // 左侧筛选条件
const rightFilterText = ref(''); // 又侧筛选条件

/* 定义emit */
const emit = defineEmits(['checkVal']);

const leftTreeData = ref([]); // 左侧tree数据
// 用于在右侧显示的数据列表
const rightData = ref([]); // 右侧列表结构数据
const rightTreeData = ref([]) // 右侧树结构数据

// 左侧输入框过滤事件
const filterLeftNode = (value, data) => {
  if (!value) return true
  return data[props.defaultProps.label].includes(value)
}

// 右侧输入框过滤事件
const filterRightNode = (value, data) => {
  if (!value) return true
  return data[props.defaultProps.label].includes(value)
}


// 右侧数据按顺序排序
const sortRightListByKey = () => {
  if (!props.isSort) return rightData.value
  return rightData.value.sort((a, b) => a[props.nodeKey] - b[props.nodeKey])
}

// 递归函数，用于构建只包含 ids 数组中 id 的树结构
const filterTreeByIds = (treeData, ids) => {
  return treeData.map(node => {
    // 创建一个新节点对象，避免直接修改原始数据
    const newNode = {...node};
    newNode[props.defaultProps.disabled] = false

    // 如果当前节点的 id 在 ids 中，保留这个节点及其子节点
    if (ids.includes(node[props.nodeKey])) {
      // 递归地过滤子节点
      newNode[props.defaultProps.children] = filterTreeByIds(node[props.defaultProps.children] || [], ids);
    } else {
      // 如果当前节点的 id 不在 ids 中，但有子节点，递归地过滤子节点
      // 同时，如果子节点中有至少一个节点的 id 在 ids 中，保留当前节点
      newNode[props.defaultProps.children] = filterTreeByIds(node[props.defaultProps.children] || [], ids).filter(child => child !== null);
    }

    // 如果当前节点的 id 不在 ids 中，且没有子节点或子节点都不在 ids 中，则不保留这个节点
    if (!ids.includes(node[props.nodeKey]) && (!newNode[props.defaultProps.children] || newNode[props.defaultProps.children].length === 0)) {
      return null;
    }

    // 返回新的节点对象
    return newNode;
  }).filter(node => node !== null); // 过滤掉 null 节点
}

// 去右边
const toRight = () => {
  /*  右侧显示的数据获取 */
  rightTreeData.value = getRightTreeData()
  rightData.value = getRightListData()

  // 给父组件抛出已选择的数据
  checkVal();

  /*
  *  更新移动后的左侧树的节点状态 和全选按钮状态
  *    先给所有已右移的节点设置禁用
  *    再通过递归计算是否将子节点的父节点也设置禁用（子节点全部禁用时，将其父节点也禁用）
  *
  * */
  let rids = rightData.value.map(item => item[props.nodeKey])
  setTreeIsDisabled(leftTreeData.value, rids, props.nodeKey, props.defaultProps)
  updateDisabledStatus(leftTreeData.value, props.defaultProps)
  isLeftCheckAllBoxDisabled.value = checkAllDisabled(leftTreeData.value, props.defaultProps)
};
// 去左边
const toLeft = async () => {
  if (props.isToList) {
    // 获取当前右侧选中的数据，没有就return
    let listToLeftIds = rightData.value.filter(item => item.checked).map(item => item[props.nodeKey])
    if (!listToLeftIds.length) return

    // 从右侧去掉选中的数据,并将所有数据的checked设为false，避免由索引变更导致的异常选中
    let unselectedList = rightData.value.filter(item => !item.checked)
    rightData.value.map(item => item.checked = false)
    rightData.value = unselectedList

    // 恢复选中数据在左侧的可选状态,并清除选中状态
    setTreeIsDisabled(leftTreeData.value, listToLeftIds, props.nodeKey, props.defaultProps, false)
    listToLeftIds.forEach(item => leftTreeRef.value.setChecked(item, false))
    updateDisabledStatus(leftTreeData.value, props.defaultProps)

    checkVal();
    isLeftCheckAllBoxDisabled.value = checkAllDisabled(leftTreeData.value, props.defaultProps)

  } else {
    // 获取当前右侧选中的数据，没有就return
    let treeToLeftIds = getRightTReeCheckedNodeIds()
    if (!treeToLeftIds.length) return

    // 恢复选中数据在左侧的可选状态,并清除选中状态
    setTreeIsDisabled(leftTreeData.value, treeToLeftIds, props.nodeKey, props.defaultProps, false)
    treeToLeftIds.forEach(item => leftTreeRef.value.setChecked(item, false))
    updateDisabledStatus(leftTreeData.value, props.defaultProps)

    checkVal();
    isLeftCheckAllBoxDisabled.value = checkAllDisabled(leftTreeData.value, props.defaultProps)

    rightTreeData.value = []
    rightTreeData.value = getRightTreeData()

  }


};


// 获取右侧树中选中节点的Ids
const getRightTReeCheckedNodeIds = () => {
  // 返回全部节点填false, false ；返回叶子结点填true,true
  const checkNodeIds = rightTreeRef.value.getCheckedKeys(true)
  if (!checkNodeIds.length) return []

  return checkNodeIds;
}

// 左侧数据全选操作（全不选）
const handleLeftAllCheck = () => {
  const leftTree = leftTreeRef.value
  const disabledIds = getDisabledNodeIds(leftTreeData.value, props.nodeKey, props.defaultProps)

  if (isCheckedAllLeft.value) {
    /*
    * 操作 ： 设置全选
    * 逻辑 ： 已经设置了disable的节点无法编辑选中，所以先获取所有设置了disable的节点的ids，然后将所有数据放开disable，设置全部选中，选中后再将ids中的节点设置禁用
    * */
    setFieldValue(leftTreeData.value, props.defaultProps.disabled, false, props.defaultProps)
    leftTree?.setCheckedNodes(leftTreeData.value)
    setTreeIsDisabled(leftTreeData.value, disabledIds, props.nodeKey, props.defaultProps)
    isCheckedAllLeft.value = true
  } else {
    /*
    * 操作 ： 设置全不选
    * 逻辑 ： 已经设置disabled的节点不应该改变其选中和禁用状态 ，所以先获取所有禁用数据的ids（也就是checked=true的所有当前选中状态的数据），然后取消全部的选中状态，再将ids中的节点设置为选中状态
    * */
    leftTree?.setCheckedNodes([])
    disabledIds.forEach(item => leftTreeRef.value.setChecked(item, true))
    isCheckedAllLeft.value = false
  }
}
// 左侧树节点checkbox被点击
const handleLeftCheckChange = () => {
  isCheckedAllLeft.value = checkedAllTrue(leftTreeRef.value, leftTreeData.value, props.nodeKey, props.defaultProps)
}

// 右侧树节点checkbox被点击
const handleRightCheckChange = () => {
  isCheckedAllRight.value = checkedAllTrue(rightTreeRef.value, rightTreeData.value, props.nodeKey, props.defaultProps)
}

// 右侧数据全选操作（全不选）
const handleRightAllCheck = () => {
  // list
  setFieldValue(rightData.value, 'checked', isCheckedAllRight.value, props.defaultProps)
  // tree
  rightTreeRef.value.setCheckedNodes(isCheckedAllRight.value ? rightTreeData.value : [])
}

// 返回已选数据给父组件
const checkVal = () => {

  emit('checkVal', props.isToList ? rightData.value : leftTreeRef.value.getCheckedNodes(true));
};

const walkTreeData = (nodes, selectedKeys) => {
  const ret = []
  nodes.forEach(node => {
    const newNode = {...node}
    newNode[props.defaultProps.disabled] = false

    delete newNode[props.defaultProps.children]
    node[props.defaultProps.children] && (newNode[props.defaultProps.children] = walkTreeData(node[props.defaultProps.children], selectedKeys))
    if (selectedKeys.includes(newNode[props.nodeKey]) || newNode[props.defaultProps.children] && newNode[props.defaultProps.children].length) {
      ret.push(newNode)
    }
  })

  return ret
}

// 获取右侧list结构数据
const getRightListData = () => {
  /*  右侧list结构数据获取 */
  if (!currentLeftUseableNodes.value.length) return []

  const newArr = rightData.value.concat(currentLeftUseableNodes.value);
  let obj: any = {};
  // 去重
  let peon: any = newArr.reduce((cur, next) => {
    obj[next[props.nodeKey]] ? '' : (obj[next[props.nodeKey]] = true && cur.push(next));
    cur.checked = false
    return cur;
  }, []); // 设置cur默认类型为数组，并且初始值为空的数组

  return peon
}


// 获取右侧树结构数据
const getRightTreeData = () => {
  if (!leftTreeRef.value || !rightTreeRef.value) return []

  const checkedKeys = leftTreeRef.value.getCheckedKeys(false) // 当前选中节点 key 的数组
  const halfCheckedKeys = leftTreeRef.value.getHalfCheckedKeys() // 目前半选中的节点的 key 所组成的数组
  const allCheckedKeys = halfCheckedKeys.concat(checkedKeys)
  if (allCheckedKeys && allCheckedKeys.length) {
    return walkTreeData(leftTreeData.value, allCheckedKeys)
  }
}


// 获取左侧树当前所选的可进行右移操作的数据
const currentLeftUseableNodes = computed(() => {
  if (!leftTreeRef.value) return []

  // 返回全部节点填false ；返回叶子结点填true
  const checkNodes = leftTreeRef.value.getCheckedNodes(true); // 将返回当前选中节点的节点数组
  if (!checkNodes.length) return []

  // 过滤当前已选，如果没有选择新的数据就return
  let useableNodes = checkNodes.filter(item => !item[props.defaultProps.disabled])
  if (!useableNodes.length) return []

  return useableNodes
})

// 左移按钮disabled计算
const isToLeftBtnDisabled = computed(() => {
  let checkNodes = []
  rightTreeRef.value && (checkNodes = rightTreeRef.value.getCheckedNodes(false, false)) // tree选择的节点
  const listToLeftIds = rightData.value.filter(item => item.checked).map(item => item[props.nodeKey]) // list选择的节点

  return !(listToLeftIds.length || checkNodes.length)
})

// 监听右侧数据变化，判断右侧全选框是否选中
watch(() => rightData.value, (newData) => {
  if (!newData) return
  isCheckedAllRight.value = newData.length && newData.every(item => item.checked)
}, {
  deep: true,
  immediate: true
})

// 初始化操作，将传参的默认选中节点传递并显示到右侧
watch(() => props.defaultSelectionKeys, (newKeys) => {
  if (!newKeys) return

  nextTick(async () => {
    // 设置目前选中的节点
    await leftTreeRef.value.setCheckedKeys(newKeys)
    toRight()

  })

}, {
  deep: true,
  immediate: true
})

// 初始化操作，将传参的默认选中节点传递并显示到右侧
watch(() => props.leftData, (newData) => {
  if (!newData) return
  leftTreeData.value = deepClone(newData)
  setFieldValue(leftTreeData.value, props.defaultProps.disabled, false, props.defaultProps)

}, {
  deep: true,
  immediate: true
})

watch(leftFilterText, (val) => {
  leftTreeRef.value!.filter(val)
})


</script>

<style lang="scss" scoped>
.z-tree-transfer {
  display: flex;
  height: 500px;
  width: 800px;
  box-sizing: border-box;

  .btn-div {
    flex: 1;
    height: 60%;
    margin: auto;
    display: flex;
    flex-direction: column;
    justify-content: space-evenly;
    align-items: center;

    .btn-item {
      :deep(svg), :deep(.el-icon) {
        height: 1.6em !important;
        width: 1.6em !important;
      }

    }
  }

  .left-content {
    width: 45%;
    border: 1px solid #dcdfe6;
    box-sizing: border-box;
    padding: 5px 10px;

    .list {
      width: 100%;
      height: 100%;
      display: flex;
      flex-direction: column;
      overflow: hidden;

      .left-tree {
        width: calc(100% - 5px);
        height: 100%;
        overflow: auto;
        margin-top: 10px;
        padding-right: 5px;
      }
    }
  }

  .righ-content {
    box-sizing: border-box;
    border: 1px solid #dcdfe6;
    padding: 5px 10px;
    width: 45%;
    overflow: auto;

    .right_item {
      text-align: left;
    }

    .list {
      height: 100%;
      display: flex;
      flex-direction: column;
    }

  }

  .left_lowline {
    display: flex;
    align-items: center;
  }

  .right_lowline {
    display: flex;
    align-items: center;
  }


  :deep(.el-input__wrapper) {
    position: relative;

    .el-input__inner {
      padding-right: 18px;
    }

    .el-input__suffix {
      position: absolute;
      right: 8px;
      top: 50%;
      transform: translateY(-50%);
    }
  }

  // 滚动条宽度
  ::-webkit-scrollbar {
    width: 6px;
    height: 6px;
  }

  // 滚动条轨道
  ::-webkit-scrollbar-track {
    background: rgb(239, 239, 239);
    border-radius: 2px;
  }

  // 小滑块
  ::-webkit-scrollbar-thumb {
    background: #40a0ff49;
    border-radius: 2px;
  }

  ::-webkit-scrollbar-thumb:hover {
    background: #40a0ff;
  }

  :deep(.el-button:focus) {
    outline: none;
  }

  :deep(.el-tree) {
    display: inline-block;
    min-width: 100%;

    .el-tree-node__content {
      //margin-right: 5px;
    }
  }

}

</style>